// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/compiler/frame-states.h"

#include "src/base/functional.h"
#include "src/callable.h"
#include "src/compiler/graph.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node.h"
#include "src/handles-inl.h"
#include "src/objects-inl.h"

namespace v8 {
namespace internal {
    namespace compiler {

        size_t hash_value(OutputFrameStateCombine const& sc)
        {
            return base::hash_value(sc.parameter_);
        }

        std::ostream& operator<<(std::ostream& os, OutputFrameStateCombine const& sc)
        {
            if (sc.parameter_ == OutputFrameStateCombine::kInvalidIndex)
                return os << "Ignore";
            return os << "PokeAt(" << sc.parameter_ << ")";
        }

        bool operator==(FrameStateInfo const& lhs, FrameStateInfo const& rhs)
        {
            return lhs.type() == rhs.type() && lhs.bailout_id() == rhs.bailout_id() && lhs.state_combine() == rhs.state_combine() && lhs.function_info() == rhs.function_info();
        }

        bool operator!=(FrameStateInfo const& lhs, FrameStateInfo const& rhs)
        {
            return !(lhs == rhs);
        }

        size_t hash_value(FrameStateInfo const& info)
        {
            return base::hash_combine(static_cast<int>(info.type()), info.bailout_id(),
                info.state_combine());
        }

        std::ostream& operator<<(std::ostream& os, FrameStateType type)
        {
            switch (type) {
            case FrameStateType::kInterpretedFunction:
                os << "INTERPRETED_FRAME";
                break;
            case FrameStateType::kArgumentsAdaptor:
                os << "ARGUMENTS_ADAPTOR";
                break;
            case FrameStateType::kConstructStub:
                os << "CONSTRUCT_STUB";
                break;
            case FrameStateType::kBuiltinContinuation:
                os << "BUILTIN_CONTINUATION_FRAME";
                break;
            case FrameStateType::kJavaScriptBuiltinContinuation:
                os << "JAVA_SCRIPT_BUILTIN_CONTINUATION_FRAME";
                break;
            case FrameStateType::kJavaScriptBuiltinContinuationWithCatch:
                os << "JAVA_SCRIPT_BUILTIN_CONTINUATION_WITH_CATCH_FRAME";
                break;
            }
            return os;
        }

        std::ostream& operator<<(std::ostream& os, FrameStateInfo const& info)
        {
            os << info.type() << ", " << info.bailout_id() << ", "
               << info.state_combine();
            Handle<SharedFunctionInfo> shared_info;
            if (info.shared_info().ToHandle(&shared_info)) {
                os << ", " << Brief(*shared_info);
            }
            return os;
        }

        namespace {

            // Lazy deopt points where the frame state is assocated with a call get an
            // additional parameter for the return result from the call. The return result
            // is added by the deoptimizer and not explicitly specified in the frame state.
            // Lazy deopt points which can catch exceptions further get an additional
            // parameter, namely the exception thrown. The exception is also added by the
            // deoptimizer.
            uint8_t DeoptimizerParameterCountFor(ContinuationFrameStateMode mode)
            {
                switch (mode) {
                case ContinuationFrameStateMode::EAGER:
                    return 0;
                case ContinuationFrameStateMode::LAZY:
                    return 1;
                case ContinuationFrameStateMode::LAZY_WITH_CATCH:
                    return 2;
                }
                UNREACHABLE();
            }

            Node* CreateBuiltinContinuationFrameStateCommon(
                JSGraph* jsgraph, FrameStateType frame_type, Builtins::Name name,
                Node* closure, Node* context, Node** parameters, int parameter_count,
                Node* outer_frame_state,
                Handle<SharedFunctionInfo> shared = Handle<SharedFunctionInfo>())
            {
                Isolate* const isolate = jsgraph->isolate();
                Graph* const graph = jsgraph->graph();
                CommonOperatorBuilder* const common = jsgraph->common();

                BailoutId bailout_id = Builtins::GetContinuationBailoutId(name);
                Callable callable = Builtins::CallableFor(isolate, name);

                const Operator* op_param = common->StateValues(parameter_count, SparseInputMask::Dense());
                Node* params_node = graph->NewNode(op_param, parameter_count, parameters);

                const FrameStateFunctionInfo* state_info = common->CreateFrameStateFunctionInfo(frame_type, parameter_count, 0,
                    shared);
                const Operator* op = common->FrameState(
                    bailout_id, OutputFrameStateCombine::Ignore(), state_info);

                Node* frame_state = graph->NewNode(
                    op, params_node, jsgraph->EmptyStateValues(), jsgraph->EmptyStateValues(),
                    context, closure, outer_frame_state);

                return frame_state;
            }

        } // namespace

        Node* CreateStubBuiltinContinuationFrameState(
            JSGraph* jsgraph, Builtins::Name name, Node* context,
            Node* const* parameters, int parameter_count, Node* outer_frame_state,
            ContinuationFrameStateMode mode)
        {
            Isolate* isolate = jsgraph->isolate();
            Callable callable = Builtins::CallableFor(isolate, name);
            CallInterfaceDescriptor descriptor = callable.descriptor();

            std::vector<Node*> actual_parameters;
            // Stack parameters first. Depending on {mode}, final parameters are added
            // by the deoptimizer and aren't explicitly passed in the frame state.
            int stack_parameter_count = descriptor.GetParameterCount() - DeoptimizerParameterCountFor(mode);
            // Reserving space in the vector, except for the case where
            // stack_parameter_count is -1.
            actual_parameters.reserve(stack_parameter_count >= 0
                    ? stack_parameter_count + descriptor.GetRegisterParameterCount()
                    : 0);
            for (int i = 0; i < stack_parameter_count; ++i) {
                actual_parameters.push_back(
                    parameters[descriptor.GetRegisterParameterCount() + i]);
            }
            // Register parameters follow, context will be added by instruction selector
            // during FrameState translation.
            for (int i = 0; i < descriptor.GetRegisterParameterCount(); ++i) {
                actual_parameters.push_back(parameters[i]);
            }

            return CreateBuiltinContinuationFrameStateCommon(
                jsgraph, FrameStateType::kBuiltinContinuation, name,
                jsgraph->UndefinedConstant(), context, actual_parameters.data(),
                static_cast<int>(actual_parameters.size()), outer_frame_state);
        }

        Node* CreateJavaScriptBuiltinContinuationFrameState(
            JSGraph* jsgraph, const SharedFunctionInfoRef& shared, Builtins::Name name,
            Node* target, Node* context, Node* const* stack_parameters,
            int stack_parameter_count, Node* outer_frame_state,
            ContinuationFrameStateMode mode)
        {
            Isolate* const isolate = jsgraph->isolate();
            Callable const callable = Builtins::CallableFor(isolate, name);

            // Depending on {mode}, final parameters are added by the deoptimizer
            // and aren't explicitly passed in the frame state.
            DCHECK_EQ(Builtins::GetStackParameterCount(name) + 1, // add receiver
                stack_parameter_count + DeoptimizerParameterCountFor(mode));

            Node* argc = jsgraph->Constant(Builtins::GetStackParameterCount(name));

            // Stack parameters first. They must be first because the receiver is expected
            // to be the second value in the translation when creating stack crawls
            // (e.g. Error.stack) of optimized JavaScript frames.
            std::vector<Node*> actual_parameters;
            for (int i = 0; i < stack_parameter_count; ++i) {
                actual_parameters.push_back(stack_parameters[i]);
            }

            // Register parameters follow stack paraemters. The context will be added by
            // instruction selector during FrameState translation.
            actual_parameters.push_back(target);
            actual_parameters.push_back(jsgraph->UndefinedConstant());
            actual_parameters.push_back(argc);

            return CreateBuiltinContinuationFrameStateCommon(
                jsgraph,
                mode == ContinuationFrameStateMode::LAZY_WITH_CATCH
                    ? FrameStateType::kJavaScriptBuiltinContinuationWithCatch
                    : FrameStateType::kJavaScriptBuiltinContinuation,
                name, target, context, &actual_parameters[0],
                static_cast<int>(actual_parameters.size()), outer_frame_state,
                shared.object());
        }

    } // namespace compiler
} // namespace internal
} // namespace v8
